package aceim.app.utils;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import aceim.api.dataentity.Buddy;
import aceim.api.dataentity.BuddyGroup;
import aceim.api.dataentity.ConnectionState;
import aceim.api.dataentity.MultiChatRoom;
import aceim.api.dataentity.OnlineInfo;
import aceim.api.dataentity.ProtocolOption;
import aceim.api.service.ApiConstants;
import aceim.api.utils.Logger;
import aceim.api.utils.Logger.LoggerLevel;
import aceim.app.dataentity.Account;
import aceim.app.dataentity.AccountOptionKeys;
import aceim.app.dataentity.AccountService;
import aceim.app.service.ServiceUtils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Xml;
public final class DataStorage {
private static final String ATTR_CONNECTION_STATE = "connection_state";
//private static final String ATTR_LAST_UPDATE = "last_update";
private static final String TAG_ACCOUNTS = "accounts";
private static final String ATTR_COLLAPSED = "is_collapsed";
private static final String TAG_GROUP = "group";
private static final String TAG_GROUPS = "groups";
private static final String ATTR_GROUPS = TAG_GROUPS;
private static final String ATTR_UNREAD = "unread";
private static final String ATTR_ID = "id";
private static final String ATTR_TYPE = "type";
private static final String ATTR_VALUE = "value";
private static final String ATTR_GROUP_ID = "group_id";
private static final String TAG_NAME = "name";
private static final String TAG_BUDDY_NAME = "buddy_name";
private static final String TAG_GROUP_NAME = "group_name";
private static final String TAG_BUDDY = "buddy";
private static final String TAG_CHAT = "chat";
private static final String ATTR_BUDDIES = "buddies";
private static final String TAG_BUDDIES = "buddies";
private static final String TAG_FEATURES = "features";
private static final String TAG_FEATURE = "feature";
private static final String TAG_XSTATUS_TEXT = "xstatus_text";
private static final String TAG_XSTATUS_NAME = "xstatus_name";
//private static final String ATTR_STATUS = "status";
private static final String ATTR_PROTOCOL_UID = "protocol_uid";
private static final String ATTR_PROTOCOL_NAME = "protocol_name";
private static final String ATTR_PROTOCOL_SERVICE_CLASS_NAME = "protocol_service_class_name";
private static final String TAG_ACCOUNT = "account";
private static final String XML_ENCODING = "UTF-16LE";
private static final String XMLPARAMS_TOTAL = "XmlTotalParams";
static final String PREFERENCES_FILEEXT = ".preferences";
private static final String XML_NAMESPACE = "aceim.app";
//private static final String XML_NAMESPACE_OLD = "ua.snuk182.asia";
private static final String SHARED_PREFERENCES = "ProtocolSharedPrefs";
private Context mContext;
public DataStorage(Context context) {
this.mContext = context;
}
public void saveProtocolOptions(final List<ProtocolOption> options, final String storageName) {
Logger.log("Save protocol options for " + storageName, LoggerLevel.VERBOSE);
Runnable r = new Runnable() {
@Override
public void run() {
SharedPreferences.Editor preferences = mContext.getSharedPreferences(storageName, ServiceUtils.getAccessMode()).edit();
for (ProtocolOption o : options) {
preferences.putString(o.getKey(), o.getValue());
}
preferences.commit();
}
};
Executors.defaultThreadFactory().newThread(r).start();
}
public void removeProtocolOptions(final Set<String> set, final String storageName) {
Logger.log("Remove protocol options " + storageName, LoggerLevel.VERBOSE);
Runnable r = new Runnable() {
@Override
public void run() {
SharedPreferences.Editor preferences = mContext.getSharedPreferences(storageName, ServiceUtils.getAccessMode()).edit();
preferences.clear();
preferences.commit();
}
};
Executors.defaultThreadFactory().newThread(r).start();
}
public synchronized String getProtocolOptionValue(String key, Account account) {
Logger.log("Value requested for protocol option " + key + " for " + account.getAccountId(), LoggerLevel.VERBOSE);
if (key == null) {
return null;
}
String storageName = account.getAccountId()+" "+SHARED_PREFERENCES;
SharedPreferences preferences = mContext.getSharedPreferences(storageName, ServiceUtils.getAccessMode());
return preferences.getString(key, null);
}
public synchronized List<Account> getAccounts() {
Logger.log("Get all saved accounts", LoggerLevel.VERBOSE);
try {
//context.getFileStreamPath(XMLPARAMS_TOTAL).delete();
List<Account> accounts = getAccountHeaders();
for (Account account : accounts) {
try {
getAccount(account, true);
} catch (Exception e) {
Logger.log(e);
}
}
return accounts;
} catch (Exception e1) {
Logger.log(e1);
return new ArrayList<Account>();
}
}
public synchronized void saveAccounts(List<AccountService> accounts) {
Logger.log("Save accounts request", LoggerLevel.VERBOSE);
for (AccountService account : accounts) {
saveAccount(account.getAccount(), false);
}
saveServiceState(accounts);
}
public synchronized void saveServiceState(List<AccountService> accounts) {
Logger.log("Save service state request", LoggerLevel.VERBOSE);
List<Account> accountViews = new LinkedList<Account>();
for (AccountService service : accounts) {
accountViews.add(service.getAccount());
}
try {
saveAccountHeaders(accountViews);
} catch (Exception e) {
Logger.log(e);
}
}
public void removeAccount(Account account) {
mContext.deleteFile(account.getAccountId());
mContext.deleteFile(account.getFilename() + PREFERENCES_FILEEXT);
try {
List<Account> acco = getAccountHeaders();
for (int i = acco.size() - 1; i >= 0; i--) {
if (acco.get(i).getProtocolUid().equalsIgnoreCase(account.getProtocolUid())) {
acco.remove(i);
break;
}
}
saveAccountHeaders(acco);
Logger.log("Account removed #" + account, LoggerLevel.VERBOSE);
} catch (Exception e) {
Logger.log(e);
}
}
public void saveAccount(final Account account) {
saveAccount(account, false);
}
public void saveAccount(final Account account, final boolean saveHeaders) {
saveAccount(account, null, saveHeaders);
}
public void saveAccount(final Account account, List<ProtocolOption> options, final boolean saveHeaders) {
Logger.log("Save account " + account + (saveHeaders ? " with headers" : ", no headers"), LoggerLevel.VERBOSE);
if (account == null) {
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
saveAccountInternal(account, saveHeaders);
}
};
Executors.defaultThreadFactory().newThread(r).start();
if (options != null) {
saveProtocolOptions(options, account.getAccountId() + " " + SHARED_PREFERENCES);
}
}
private synchronized void saveAccountInternal(Account account, boolean saveHeaders) {
boolean saveNotInList = mContext.getSharedPreferences(account.getAccountId(), 0).getBoolean(AccountOptionKeys.SAVE_NOT_IN_LIST.name(), false);
XmlSerializer serializer = Xml.newSerializer();
try {
serializer.setOutput(new BufferedOutputStream(mContext.openFileOutput(account.getFilename() + PREFERENCES_FILEEXT, ServiceUtils.getAccessMode())), XML_ENCODING);
serializer.startDocument(XML_ENCODING, true);
serializer.startTag(XML_NAMESPACE, TAG_ACCOUNT);
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_UID, account.getProtocolUid().trim());
if (account.getProtocolName() != null) {
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_NAME, account.getProtocolName().trim());
}
if (account.getProtocolServicePackageName() != null) {
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_SERVICE_CLASS_NAME, account.getProtocolServicePackageName().trim());
}
if (account.getOwnName() != null) {
serializer.startTag(XML_NAMESPACE, TAG_NAME);
serializer.text(account.getOwnName());
serializer.endTag(XML_NAMESPACE, TAG_NAME);
}
if (account.getOnlineInfo().getXstatusName() != null) {
serializer.startTag(XML_NAMESPACE, TAG_XSTATUS_NAME);
serializer.text(account.getOnlineInfo().getXstatusName());
serializer.endTag(XML_NAMESPACE, TAG_XSTATUS_NAME);
}
if (account.getOnlineInfo().getXstatusDescription() != null) {
serializer.startTag(XML_NAMESPACE, TAG_XSTATUS_TEXT);
serializer.text(account.getOnlineInfo().getXstatusDescription());
serializer.endTag(XML_NAMESPACE, TAG_XSTATUS_TEXT);
}
serializer.startTag(XML_NAMESPACE, TAG_FEATURES);
for (String feature: account.getOnlineInfo().getFeatures().keySet()) {
Object value = account.getOnlineInfo().getFeatures().get(feature);
serializer.startTag(XML_NAMESPACE, TAG_FEATURE);
serializer.attribute(XML_NAMESPACE, ATTR_ID, feature);
serializer.attribute(XML_NAMESPACE, ATTR_TYPE, value.getClass().getName());
serializer.attribute(XML_NAMESPACE, ATTR_VALUE, value.toString());
serializer.endTag(XML_NAMESPACE, TAG_FEATURE);
}
serializer.endTag(XML_NAMESPACE, TAG_FEATURES);
serializer.startTag(XML_NAMESPACE, TAG_GROUPS);
serializer.attribute(XML_NAMESPACE, ATTR_GROUPS, Integer.toString(account.getBuddyGroupList().size()));
List<BuddyGroup> buddyGroups = new ArrayList<BuddyGroup>();
buddyGroups.addAll(account.getBuddyGroupList());
BuddyGroup noGroup = new BuddyGroup(ApiConstants.NO_GROUP_ID, account.getProtocolUid(), account.getServiceId());
noGroup.getBuddyList().addAll(account.getNoGroupBuddies());
buddyGroups.add(noGroup);
for (BuddyGroup group : buddyGroups) {
serializer.startTag(XML_NAMESPACE, TAG_GROUP);
serializer.attribute(XML_NAMESPACE, ATTR_ID, group.getId());
serializer.attribute(XML_NAMESPACE, ATTR_COLLAPSED, Boolean.toString(group.isCollapsed()));
if (group.getName() != null) {
serializer.startTag(XML_NAMESPACE, TAG_GROUP_NAME);
serializer.text(group.getName());
serializer.endTag(XML_NAMESPACE, TAG_GROUP_NAME);
}
serializer.startTag(XML_NAMESPACE, TAG_BUDDIES);
serializer.attribute(XML_NAMESPACE, ATTR_BUDDIES, Integer.toString(account.getBuddyList().size()));
for (Buddy buddy : group.getBuddyList()) {
if (buddy.getGroupId().equals(ApiConstants.NOT_IN_LIST_GROUP_ID) && !saveNotInList) {
continue;
}
if (buddy instanceof MultiChatRoom) {
serializer.startTag(XML_NAMESPACE, TAG_CHAT);
} else {
serializer.startTag(XML_NAMESPACE, TAG_BUDDY);
}
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_UID, buddy.getProtocolUid().trim());
serializer.attribute(XML_NAMESPACE, ATTR_GROUP_ID, buddy.getGroupId());
serializer.attribute(XML_NAMESPACE, ATTR_ID, Integer.toString(buddy.getId()));
serializer.attribute(XML_NAMESPACE, ATTR_UNREAD, Byte.toString(buddy.getUnread()));
/*serializer.startTag(XML_NAMESPACE, TAG_FEATURES);
for (String feature: buddy.getOnlineInfo().getFeatures().keySet()) {
if (feature.equals(ApiConstants.FEATURE_STATUS)) {
continue;
}
Object value = buddy.getOnlineInfo().getFeatures().get(feature);
serializer.startTag(XML_NAMESPACE, TAG_FEATURE);
serializer.attribute(XML_NAMESPACE, ATTR_ID, feature);
serializer.attribute(XML_NAMESPACE, ATTR_TYPE, value.getClass().getName());
serializer.attribute(XML_NAMESPACE, ATTR_VALUE, value.toString());
serializer.endTag(XML_NAMESPACE, TAG_FEATURE);
}
serializer.endTag(XML_NAMESPACE, TAG_FEATURES);*/
if (buddy.getName() != null) {
serializer.startTag(XML_NAMESPACE, TAG_BUDDY_NAME);
serializer.text(buddy.getName());
serializer.endTag(XML_NAMESPACE, TAG_BUDDY_NAME);
}
if (buddy instanceof MultiChatRoom) {
serializer.endTag(XML_NAMESPACE, TAG_CHAT);
} else {
serializer.endTag(XML_NAMESPACE, TAG_BUDDY);
}
}
serializer.endTag(XML_NAMESPACE, TAG_BUDDIES);
serializer.endTag(XML_NAMESPACE, TAG_GROUP);
}
serializer.endTag(XML_NAMESPACE, TAG_GROUPS);
serializer.endTag(XML_NAMESPACE, TAG_ACCOUNT);
serializer.endDocument();
if (saveHeaders) {
List<Account> accounts;
try {
accounts = getAccountHeaders();
} catch (XmlPullParserException e) {
accounts = new ArrayList<Account>(1);
}
boolean found = false;
for (Account acco : accounts) {
if (acco.getProtocolUid().equalsIgnoreCase(account.getProtocolUid())) {
// acco.merge(account);
found = true;
}
}
if (!found) {
accounts.add(account);
}
saveAccountHeaders(accounts);
}
Logger.log("Account saved " + account, LoggerLevel.VERBOSE);
} catch (Exception e) {
Logger.log(e);
}
}
private synchronized void saveAccountHeaders(List<Account> accounts) throws IllegalArgumentException, IllegalStateException, FileNotFoundException, IOException {
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(new BufferedOutputStream(mContext.openFileOutput(XMLPARAMS_TOTAL, ServiceUtils.getAccessMode())), XML_ENCODING);
serializer.startDocument(XML_ENCODING, true);
serializer.startTag(XML_NAMESPACE, TAG_ACCOUNTS);
for (Account account : accounts) {
serializer.startTag(XML_NAMESPACE, TAG_ACCOUNT);
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_UID, account.getProtocolUid());
if (account.getProtocolName() != null) {
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_NAME, account.getProtocolName());
}
if (account.getProtocolServicePackageName() != null) {
serializer.attribute(XML_NAMESPACE, ATTR_PROTOCOL_SERVICE_CLASS_NAME, account.getProtocolServicePackageName());
}
serializer.attribute(XML_NAMESPACE, ATTR_CONNECTION_STATE, account.getConnectionState().name());
serializer.endTag(XML_NAMESPACE, TAG_ACCOUNT);
}
serializer.endTag(XML_NAMESPACE, TAG_ACCOUNTS);
serializer.endDocument();
}
static List<Account> readFileWithAccountList(InputStream stream, String encoding) throws XmlPullParserException, IOException {
List<Account> accounts = new ArrayList<Account>();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, encoding);
int eventType = parser.getEventType();
Account account = null;
boolean done = false;
while (eventType != XmlPullParser.END_DOCUMENT && !done) {
String name = null;
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(TAG_ACCOUNT)) {
String protocolUid = parser.getAttributeValue(XML_NAMESPACE, ATTR_PROTOCOL_UID);
String protocolName = parser.getAttributeValue(XML_NAMESPACE, ATTR_PROTOCOL_NAME);
String protocolServiceClassName = parser.getAttributeValue(XML_NAMESPACE, ATTR_PROTOCOL_SERVICE_CLASS_NAME);
if (protocolUid != null) {
account = new Account((byte) accounts.size(), protocolUid, protocolName, protocolServiceClassName);
String connectionState = parser.getAttributeValue(XML_NAMESPACE, ATTR_CONNECTION_STATE);
if (connectionState != null) {
account.setConnectionState(ConnectionState.valueOf(connectionState));
} else {
account.setConnectionState(ConnectionState.DISCONNECTED);
}
}
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(TAG_ACCOUNT) && account != null) {
accounts.add(account);
} else if (name.equalsIgnoreCase(TAG_ACCOUNTS)) {
done = true;
}
break;
}
eventType = parser.next();
}
return accounts;
}
private synchronized List<Account> getAccountHeaders() throws XmlPullParserException {
try {
return readFileWithAccountList(mContext.openFileInput(XMLPARAMS_TOTAL), XML_ENCODING);
} catch (IOException e) {
Logger.log(e);
}
return Collections.emptyList();
}
public synchronized Account getAccount(Account account, boolean getBuddies) throws XmlPullParserException, IOException {
if (account == null) {
return null;
}
XmlPullParser parser = Xml.newPullParser();
parser.setInput(mContext.openFileInput(account.getFilename() + PREFERENCES_FILEEXT), XML_ENCODING);
int eventType = parser.getEventType();
List<BuddyGroup> buddyGroupList = new ArrayList<BuddyGroup>();
Buddy buddy = null;
BuddyGroup group = null;
boolean done = false;
while (eventType != XmlPullParser.END_DOCUMENT && !done) {
String name = null;
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(TAG_FEATURE)) {
String fid = parser.getAttributeValue(XML_NAMESPACE, ATTR_ID);
String className = parser.getAttributeValue(XML_NAMESPACE, ATTR_TYPE);
String value = parser.getAttributeValue(XML_NAMESPACE, ATTR_VALUE);
OnlineInfo info;
if (buddy != null) {
info = buddy.getOnlineInfo();
} else if (account != null) {
info = account.getOnlineInfo();
} else {
parser.next();
continue;
}
if (className.equals(Byte.class.getName())) {
info.getFeatures().putByte(fid, Byte.parseByte(value));
} else if (className.equals(Short.class.getName())) {
info.getFeatures().putShort(fid, Short.parseShort(value));
} else if (className.equals(Integer.class.getName())) {
info.getFeatures().putInt(fid, Integer.parseInt(value));
} else if (className.equals(Long.class.getName())) {
info.getFeatures().putLong(fid, Long.parseLong(value));
} else if (className.equals(Boolean.class.getName())) {
info.getFeatures().putBoolean(fid, Boolean.parseBoolean(value));
} else if (className.equals(Double.class.getName())) {
info.getFeatures().putDouble(fid, Double.parseDouble(value));
} else if (className.equals(Character.class.getName())) {
info.getFeatures().putChar(fid, value.charAt(0));
} else if (className.equals(Float.class.getName())) {
info.getFeatures().putFloat(fid, Float.parseFloat(value));
} else {
info.getFeatures().putString(fid, value);
}
} else if (account != null) {
if (name.equalsIgnoreCase(TAG_NAME)) {
if (account != null) {
account.setOwnName(parser.nextText());
}
} else if (name.equalsIgnoreCase(TAG_XSTATUS_NAME)) {
if (account != null) {
account.getOnlineInfo().setXstatusName(parser.nextText());
}
} else if (name.equalsIgnoreCase(TAG_XSTATUS_TEXT)) {
if (account != null) {
account.getOnlineInfo().setXstatusDescription(parser.nextText());
}
} else if (name.equalsIgnoreCase(TAG_BUDDY_NAME)) {
if (buddy != null) {
buddy.setName(parser.nextText());
}
} else if (name.equalsIgnoreCase(TAG_GROUP_NAME)) {
if (group != null) {
group.setName(parser.nextText());
}
} else if (name.equalsIgnoreCase(TAG_BUDDY) && getBuddies) {
buddy = new Buddy(parser.getAttributeValue(XML_NAMESPACE, ATTR_PROTOCOL_UID), account.getProtocolUid(), account.getProtocolName(), account.getServiceId());
fillBuddy(buddy, parser);
} else if (name.equalsIgnoreCase(TAG_CHAT) && getBuddies) {
buddy = new MultiChatRoom(parser.getAttributeValue(XML_NAMESPACE, ATTR_PROTOCOL_UID), account.getProtocolUid(), account.getProtocolName(), account.getServiceId());
fillBuddy(buddy, parser);
} else if (name.equalsIgnoreCase(TAG_GROUP) && getBuddies) {
group = new BuddyGroup(parser.getAttributeValue(XML_NAMESPACE, ATTR_ID), account.getProtocolUid(), account.getServiceId());
group.setCollapsed(Boolean.parseBoolean(parser.getAttributeValue(XML_NAMESPACE, ATTR_COLLAPSED)));
}
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(TAG_BUDDY) || name.equalsIgnoreCase(TAG_CHAT)) {
if (buddy != null && group != null) {
group.getBuddyList().add(buddy);
buddy.getOnlineInfo().getFeatures().remove(ApiConstants.FEATURE_STATUS);
buddy = null;
}
} else if (name.equalsIgnoreCase(TAG_GROUP)) {
if (group != null) {
buddyGroupList.add(group);
group = null;
}
} else if (name.equalsIgnoreCase(TAG_ACCOUNT)) {
done = true;
}
break;
}
eventType = parser.next();
}
account.setBuddyList(buddyGroupList);
return account;
}
private void fillBuddy(Buddy buddy, XmlPullParser parser) {
buddy.setGroupId(parser.getAttributeValue(XML_NAMESPACE, ATTR_GROUP_ID));
buddy.setId(Integer.parseInt(parser.getAttributeValue(XML_NAMESPACE, ATTR_ID)));
buddy.setUnread(Byte.parseByte(parser.getAttributeValue(XML_NAMESPACE, ATTR_UNREAD)));
}
public synchronized void delete(String storageName) {
Logger.log("Delete storage file " + storageName, LoggerLevel.VERBOSE);
mContext.deleteFile(storageName);
}
}